/**
 * 
 */
package com.redxiii.spike.pmi.commons;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.redxiii.spike.api.pmi.entity.Activity;
import com.redxiii.spike.api.pmi.entity.Issue;
import com.redxiii.spike.api.pmi.entity.IssuePriority;
import com.redxiii.spike.api.pmi.entity.IssueTimeEntry;
import com.redxiii.spike.api.pmi.entity.IssueType;
import com.redxiii.spike.api.pmi.entity.Member;
import com.redxiii.spike.api.pmi.entity.Project;
import com.redxiii.spike.api.pmi.entity.ProjectOutput;

/**
 * @author Daniel Filgueiras
 *
 */
public abstract class PmiDatabase {

	private static final Logger logger = LoggerFactory.getLogger(PmiDatabase.class);
	
	private CacheManager cacheManager;
	private Cache issuesCache;
	private Cache issuesByOwnerCache;
	
	private Map<String,Member> 	membersMap;
	private Map<String,Project>	projectsMap;
	private Map<String,Activity> 		activitiesMap;
	private Map<String,IssuePriority> 	prioritiesMap;
	private Map<String,IssueType>		typesMap;
	private Map<String,ProjectOutput>	outputsMap;
	
	public abstract boolean connect();
	
	public abstract Issue commitWork(IssueTimeEntry timeEntry);
	public abstract void giveBack(Issue issue, Member member);
	public abstract void closeIssue(Issue issue, Member member);
	
	protected abstract Issue loadIssue(Long id);
	protected abstract Set<Long> loadIssues(Member member);
	protected abstract Set<Number> loadActiveIssues(Member member);
	protected abstract Set<Project> loadProjects();
	protected abstract Set<Member> loadMembers();
	protected abstract Set<Activity> loadActivities();
	protected abstract Set<IssuePriority> loadIssuePriorities();
	protected abstract Set<IssueType> loadIssueTypes();
	protected abstract Set<ProjectOutput> loadOutputs();
	
	public PmiDatabase() {
		initCache();
	}
	
	protected void initCache() {
		if (cacheManager == null) {
			logger.info("Initializing cache");
			cacheManager = CacheManager.create();
			
			issuesCache = new Cache(
					new CacheConfiguration("issues", 250)
						.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
						.overflowToDisk(false)
						.eternal(false)
						.timeToLiveSeconds(300)
						.timeToIdleSeconds(300));
			
			cacheManager.addCacheIfAbsent(issuesCache);
			
			issuesByOwnerCache = new Cache(
					new CacheConfiguration("issuesByOwner", 500)
						.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
						.overflowToDisk(false)
						.eternal(false)
						.timeToLiveSeconds(60)
						.timeToIdleSeconds(60));
			
			cacheManager.addCacheIfAbsent(issuesByOwnerCache);
		}
	}
	
	public void close() {
		logger.debug("Shutting down database cache");
		cacheManager.clearAll();
		cacheManager.shutdown();
		cacheManager = null;
	}

	public void recycleCache() {
		issuesCache.removeAll();
		issuesByOwnerCache.removeAll();
		membersMap = null;
		projectsMap = null;
		activitiesMap = null;
		prioritiesMap = null;
	}
	
	/**
	 * Cache Iteration
	 */
	protected Issue getIssueFromCache(Long id) {
		
		Element element = issuesCache.get(id);
		if (element == null || element.isExpired()) {
			logger.trace("Getting issue from Database: {}", id);
			Issue issue = loadIssue(id);
			if (issue != null) {
				issuesCache.put(new Element(id, issue));
			}
			return issue;
			
		} else {
			logger.trace("Getting issue from cache: {}", id);
			return (Issue) element.getObjectValue();
		}
	}
	
	@SuppressWarnings("unchecked")
	protected SortedSet<Issue> getIssuesFromCache(Member owner) {
		SortedSet<Issue> issues = new TreeSet<Issue>();
		
		Element element = issuesByOwnerCache.get(owner.getId());
		
		if (element == null || element.isExpired()) {
			element = new Element(owner.getId(), loadIssues(owner));
			issuesByOwnerCache.put(element);
		}
		
		Set<Number> issuesIds = (Set<Number>) element.getObjectValue();
		for (Number id : issuesIds) {
			Issue issue = getIssueFromCache(id.longValue());
			if (issue != null)
				issues.add(issue);
			else
				logger.debug("Issue id {} not found", id);
		}
		
		return issues;
	}
	
	protected SortedSet<Issue> getActiveIssuesFromCache(Member owner) {
		SortedSet<Issue> issues = new TreeSet<Issue>();
		
		Set<Number> issuesIds = loadActiveIssues(owner);
		for (Number id : issuesIds) {
			Issue issue = getIssueFromCache(id.longValue());
			if (issue != null)
				issues.add(issue);
			else
				logger.debug("Issue id {} not found", id);
		}
		
		return issues;
	}
	
	protected Project getProjectFromCache(String id) {
		
		if (projectsMap == null) {
			projectsMap = new HashMap<String, Project>();
			
			for (Project project : loadProjects()) {
				projectsMap.put(project.getId(), project);
			}
		}
		return projectsMap.get(id);
	}
	
	protected ProjectOutput getOutputFromCache(String id) {
		if (outputsMap == null) {
			outputsMap = new HashMap<String, ProjectOutput>();
			
			for (ProjectOutput output : loadOutputs()) {
				outputsMap.put(output.getId(), output);
			}
		}
		return outputsMap.get(id);
	}
	
	private void initMembersCache() {
		if (membersMap == null) {
			membersMap = new HashMap<String, Member>();
			
			Set<Member> members = loadMembers();
			if (members != null) {
				for (Member member : members) {
					membersMap.put(member.getId(), member);
				}
			}
		}
	}
	
	protected Member getMemberFromCache(String id) {
		
		initMembersCache();
		
		return membersMap.get(id);
		
	}
	
	private void initActivitiesCache() {
		if (activitiesMap == null) {
			activitiesMap = new HashMap<String, Activity>();
			
			for (Activity activity : loadActivities()) {
				activitiesMap.put(activity.getId(), activity);
			}
		}
	}
	
	protected Activity getActivityFromCache(String id) {
		
		initActivitiesCache();
		
		return activitiesMap.get(id);
		
	}

	protected IssuePriority getPrioritiesFromCache(String id) {
		
		if (prioritiesMap == null) {
			prioritiesMap = new HashMap<String, IssuePriority>();
			
			for (IssuePriority priority : loadIssuePriorities()) {
				prioritiesMap.put(priority.getId(), priority);
			}
		}
		return prioritiesMap.get(id);
	}
	
	protected IssueType getTypeFromCache(String id) {
		if (typesMap == null) {
			typesMap = new HashMap<String, IssueType>();
			
			for (IssueType type : loadIssueTypes()) {
				typesMap.put(type.getId(), type);
			}
		}
		return typesMap.get(id);
	}
	
	/**
	 * Public
	 */
	public SortedSet<Issue> getMyIssues(String memberId) {
		
		Member member = getMemberFromCache(memberId);
		if (member == null)
			return null;
		
		return getIssuesFromCache(member);
	}
	
	public SortedSet<Issue> getMyActiveIssues(String memberId) {
		Member member = getMemberFromCache(memberId);
		if (member == null)
			return null;
		
		return getActiveIssuesFromCache(member);
	}
	
	public Issue getIssue(long issueId) {
		return getIssueFromCache(issueId);
	}
	
	public Activity getActivity(String id) {
		initActivitiesCache();
		
		return activitiesMap.get(id);
	}
	
	public Collection<Member> getMembers() {
		
		initMembersCache();
		
		return Collections.unmodifiableCollection(membersMap.values());
	}
	
	public Member getMemberByMail(String mail) {
		
		initMembersCache();
		
		for (Member member : membersMap.values()) {
			if (member.getMail().equals(mail))
				return member;
		}
		
		return null;
	}
	
	public Member getMemberByLogin(String login) {
		initMembersCache();
		
		for (Member member : membersMap.values()) {
			if (member.getLogin().equals(login))
				return member;
		}
		
		return null;
	}
	
	public Activity getActivityByName(String name) {
		initActivitiesCache();
		
		for (Activity activity : activitiesMap.values()) {
			if (activity.getName().equals(name))
				return activity;
		}
		
		return null;
	}
}
